package callback

import (
	"encoding/xml"
	"fmt"
	"strconv"
	"time"
)

// 参考 https://work.weixin.qq.com/api/doc/90000/90135/90239
type MsgType string
type EventType string

// 消息类型
const (
	MsgTypeText     MsgType = "text"
	MsgTypeImage    MsgType = "image"
	MsgTypeVoice    MsgType = "voice"
	MsgTypeVideo    MsgType = "video"
	MsgTypeLocation MsgType = "location"
	MsgTypeLink     MsgType = "link"
	MsgTypeEvent    MsgType = "event"
)

// 事件类型
const (
	EventTypeSubcrible      EventType = "subscribe"
	EventTypeUnSubcrible    EventType = "unsubscribe"
	EventTypeEnterAgent     EventType = "enter_agent"
	EventTypeReportLocation EventType = "location"
)

// MsgContent 用户文本消息内容
type MsgContent struct {
	ToUsername   string `xml:"ToUserName"`
	FromUsername string `xml:"FromUserName"`
	CreateTime   uint32 `xml:"CreateTime"`
	MsgType      string `xml:"MsgType"`
	Content      string `xml:"Content"`
	Msgid        string `xml:"MsgId"`
	Agentid      uint32 `xml:"AgentId"`
}

// MsgContentReply 回复用户文本消息内容
type MsgContentReply struct {
	ToUsername   string `xml:"ToUserName"`
	FromUsername string `xml:"FromUserName"`
	CreateTime   uint32 `xml:"CreateTime"`
	MsgType      string `xml:"MsgType"`
	Content      string `xml:"Content"`
}

// MsgImageReply 回复用户图片消息内容
type MsgImageReply struct {
	ToUsername    string          `xml:"ToUserName"`
	FromUsername  string          `xml:"FromUserName"`
	CreateTime    uint32          `xml:"CreateTime"`
	MsgType       string          `xml:"MsgType"` // 消息类型，此时固定为：image
	ImageResource []ImageResource `xml:"Image"`
}

// ImageResource 图片资源
type ImageResource struct {
	MediaId string `xml:"MediaId"` // 图片媒体文件id，可以调用获取媒体文件接口拉取
}

// MsgVoiceReply 回复用户语音消息内容
type MsgVoiceReply struct {
	ToUsername    string          `xml:"ToUserName"`
	FromUsername  string          `xml:"FromUserName"`
	CreateTime    uint32          `xml:"CreateTime"`
	MsgType       string          `xml:"MsgType"` // 消息类型，此时固定为：voice
	VoiceResource []VoiceResource `xml:"Voice"`
}

// VoiceResource 语音资源
type VoiceResource struct {
	MediaId string `xml:"MediaId"` // 语音文件id，可以调用获取媒体文件接口拉取
}

// MsgVideoReply 回复用户视频消息内容
type MsgVideoReply struct {
	ToUsername    string          `xml:"ToUserName"`
	FromUsername  string          `xml:"FromUserName"`
	CreateTime    uint32          `xml:"CreateTime"`
	MsgType       string          `xml:"MsgType"` // 消息类型，此时固定为：video
	VideoResource []VideoResource `xml:"Video"`
}

// VideoResource 视频资源
type VideoResource struct {
	MediaId     string `xml:"MediaId"`     // 视频文件id，可以调用获取媒体文件接口拉取
	Title       string `xml:"Title"`       // 视频消息的标题,不超过128个字节，超过会自动截断
	Description string `xml:"Description"` // 视频消息的描述,不超过512个字节，超过会自动截断
}

// MsgNewsReply 回复用户图文消息内容
type MsgNewsReply struct {
	ToUsername   string         `xml:"ToUserName"`
	FromUsername string         `xml:"FromUserName"`
	CreateTime   uint32         `xml:"CreateTime"`
	MsgType      string         `xml:"MsgType"`      // 消息类型，此时固定为：news
	ArticleCount uint32         `xml:"ArticleCount"` // 图文消息的数量
	NewsResource []NewsResource `xml:"Articles"`     // 图文资源
}

// NewsResource 图文资源
type NewsResource struct {
	Items []News `xml:"item"` // 图文列表项
}

// News 图文信息
type News struct {
	Title       string `xml:"Title"`       // 视频消息的标题,不超过128个字节，超过会自动截断
	Description string `xml:"Description"` // 视频消息的描述,不超过512个字节，超过会自动截断
	Url         string `xml:"Url"`         // 点击后跳转的链接。
	PicUrl      string `xml:"PicUrl"`      // 图文消息的图片链接，支持JPG、PNG格式，较好的效果为大图640320，小图8080。
}

// MsgTaskCardReply 回复用户任务卡片
type MsgTaskCardReply struct {
	ToUsername       string             `xml:"ToUserName"`
	FromUsername     string             `xml:"FromUserName"`
	CreateTime       uint32             `xml:"CreateTime"`
	MsgType          string             `xml:"MsgType"` // 消息类型，此时固定为：update_taskcard
	TaskCardResource []TaskCardResource `xml:"TaskCard"`
}

// TaskCardResource 任务卡片资源
type TaskCardResource struct {
	ReplaceName string `xml:"ReplaceName"` // 点击任务卡片按钮后显示的按钮名称
}

// ==========================接受的消息===========================================

// MsgImage 用户图片消息内容
type MsgImage struct {
	ToUsername   string `xml:"ToUserName"`   // 企业微信CorpID
	FromUsername string `xml:"FromUserName"` // 成员UserID
	CreateTime   uint32 `xml:"CreateTime"`   // 消息创建时间（整型）
	MsgType      string `xml:"MsgType"`      // 消息类型，此时固定为：image
	PicUrl       string `xml:"PicUrl"`       // 图片链接
	MediaId      string `xml:"MediaId"`      // 图片媒体文件id，可以调用获取媒体文件接口拉取，仅三天内有效
	Msgid        string `xml:"MsgId"`        // 消息id，64位整型
	Agentid      uint32 `xml:"AgentId"`      // 企业应用的id，整型。可在应用的设置页面查看
}

// MsgVoice 用户语音消息内容
type MsgVoice struct {
	ToUsername   string `xml:"ToUserName"`   // 企业微信CorpID
	FromUsername string `xml:"FromUserName"` // 成员UserID
	CreateTime   uint32 `xml:"CreateTime"`   // 消息创建时间（整型）
	MsgType      string `xml:"MsgType"`      // 消息类型，此时固定为：voice
	Format       string `xml:"Format"`       // 语音格式，如amr，speex等
	MediaId      string `xml:"MediaId"`      // 语音媒体文件id，可以调用获取媒体文件接口拉取数据，仅三天内有效
	Msgid        string `xml:"MsgId"`        // 消息id，64位整型
	Agentid      uint32 `xml:"AgentId"`      // 企业应用的id，整型。可在应用的设置页面查看
}

// MsgVideo 用户视频消息内容
type MsgVideo struct {
	ToUsername   string `xml:"ToUserName"`   // 企业微信CorpID
	FromUsername string `xml:"FromUserName"` // 成员UserID
	CreateTime   uint32 `xml:"CreateTime"`   // 消息创建时间（整型）
	MsgType      string `xml:"MsgType"`      // 消息类型，此时固定为：video
	ThumbMediaId string `xml:"ThumbMediaId"` // 视频消息缩略图的媒体id，可以调用获取媒体文件接口拉取数据，仅三天内有效
	MediaId      string `xml:"MediaId"`      // 视频媒体文件id，可以调用获取媒体文件接口拉取数据，仅三天内有效
	Msgid        string `xml:"MsgId"`        // 消息id，64位整型
	Agentid      uint32 `xml:"AgentId"`      // 企业应用的id，整型。可在应用的设置页面查看
}

// MsgLocation 用户位置消息内容
type MsgLocation struct {
	ToUsername   string `xml:"ToUserName"`   // 企业微信CorpID
	FromUsername string `xml:"FromUserName"` // 成员UserID
	CreateTime   uint32 `xml:"CreateTime"`   // 消息创建时间（整型）
	MsgType      string `xml:"MsgType"`      // 消息类型，此时固定为： location
	Location_X   string `xml:"Location_X"`   // 地理位置纬度
	Location_Y   string `xml:"Location_Y"`   // 地理位置经度
	Scale        uint32 `xml:"Scale"`        // 地图缩放大小
	Label        string `xml:"Label"`        //地理位置信息
	Msgid        string `xml:"MsgId"`        // 消息id，64位整型
	Agentid      uint32 `xml:"AgentId"`      // 企业应用的id，整型。可在应用的设置页面查看
}

// MsgLink 用户链接消息内容
type MsgLink struct {
	ToUsername   string `xml:"ToUserName"`   // 企业微信CorpID
	FromUsername string `xml:"FromUserName"` // 成员UserID
	CreateTime   uint32 `xml:"CreateTime"`   // 消息创建时间（整型）
	MsgType      string `xml:"MsgType"`      // 消息类型，此时固定为：link
	Title        string `xml:"Title"`        // 标题
	Description  string `xml:"Description"`  // 描述
	Url          string `xml:"Url"`          // 链接跳转的url
	PicUrl       string `xml:"PicUrl"`       // 封面缩略图的url
	Msgid        string `xml:"MsgId"`        // 消息id，64位整型
	Agentid      uint32 `xml:"AgentId"`      // 企业应用的id，整型。可在应用的设置页面查看
}

// EventSub 用户订阅事件
type EventSub struct {
	ToUsername   string `xml:"ToUserName"`   //企业微信CorpID
	FromUsername string `xml:"FromUserName"` //成员UserID
	CreateTime   uint32 `xml:"CreateTime"`   //消息创建时间（整型）
	MsgType      string `xml:"MsgType"`      //消息类型，此时固定为：event
	Event        string `xml:"Event"`        //事件类型，subscribe(关注)、unsubscribe(取消关注)
	EventKey     string `xml:"EventKey"`     //事件KEY值，此事件该值为空
	Agentid      uint32 `xml:"AgentId"`      //企业应用的id，整型。可在应用的设置页面查看
}

// EventEnterAgent 用户进入应用事件
type EventEnterAgent struct {
	ToUsername   string `xml:"ToUserName"`   //企业微信CorpID
	FromUsername string `xml:"FromUserName"` //成员UserID
	CreateTime   uint32 `xml:"CreateTime"`   //消息创建时间（整型）
	MsgType      string `xml:"MsgType"`      //消息类型，此时固定为：event
	Event        string `xml:"Event"`        //事件类型：enter_agent
	EventKey     string `xml:"EventKey"`     //事件KEY值，此事件该值为空
	Agentid      uint32 `xml:"AgentId"`      //企业应用的id，整型。可在应用的设置页面查看
}

// EventReportLocation 上报地理位置事件
type EventReportLocation struct {
	ToUsername   string `xml:"ToUserName"`   //企业微信CorpID
	FromUsername string `xml:"FromUserName"` //成员UserID
	CreateTime   uint32 `xml:"CreateTime"`   //消息创建时间（整型）
	MsgType      string `xml:"MsgType"`      //消息类型，此时固定为：event
	Event        string `xml:"Event"`        //事件类型：LOCATION
	Latitude     string `xml:"Latitude"`     // 地理位置纬度
	Longitude    string `xml:"Longitude"`    // 地理位置经度
	Precision    string `xml:"Precision"`    //地理位置精度
	EventKey     string `xml:"EventKey"`     //事件KEY值，此事件该值为空
	Agentid      uint32 `xml:"AgentId"`      //企业应用的id，整型。可在应用的设置页面查看
	AppType      string `xml:"AppType"`      //app类型，在企业微信固定返回wxwork，在微信不返回该字段
}

// MsgTypeResp 获取用户消息类型响应
type MsgTypeResp struct {
	MsgType string `xml:"MsgType"` // 消息类型
}

// EventTypeResp 获取用户事件类型响应，在MsgType为event时查询
type EventTypeResp struct {
	Event string `xml:"Event"` // 事件类型
}

// CallbackVerifyURLReq 回调验证URL请求
type CallbackVerifyURLReq struct {
	MsgSignature string
	Timestamp    string
	Nonce        string
	EchoStr      string
}

// DecryptUserMsgReq 用户消息解密请求
type DecryptUserMsgReq struct {
	MsgSignature string // 签名
	Timestamp    string
	Nonce        string
	Data         []byte // post请求发送的xml密文数据
}

// EncryptUserMsgReq 企业回复消息加密请求
type EncryptUserMsgReq struct {
	Timestamp string
	Nonce     string
	Data      string // 企业待发送的xml明文数据
}

// CallbackManager 回调管理
type CallbackManager struct {
	crypt *WXBizMsgCrypt
}

// NewCallbackManager 返回一个企业微信加解密实例
func NewCallbackManager(receiver_id string, token string, encodingAESKey string) *CallbackManager {
	wxcpt := NewWXBizMsgCrypt(token, encodingAESKey, receiver_id, XmlType)
	return &CallbackManager{crypt: wxcpt}
}

/*
   	------------使用示例一：验证回调URL---------------
   	*企业开启回调模式时，企业微信会向验证url发送一个get请求
   	假设点击验证时，企业收到类似请求：
   	* GET /cgi-bin/wxpush?msg_signature=5c45ff5e21c57e6ad56bac8758b79b1d9ac89fd3&timestamp=1409659589&nonce=263014780&echostr=P9nAzCzyDtyTWESHep1vC5X9xho%2FqYX3Zpb4yKa9SKld1DsH3Iyt3tP3zNdtp%2B4RPcs8TgAE7OaBO%2BFZXvnaqQ%3D%3D
   	* HTTP/1.1 Host: qy.weixin.qq.com

   	接收到该请求时，企业应
        1.解析出Get请求的参数，包括消息体签名(msg_signature)，时间戳(timestamp)，随机数字串(nonce)以及企业微信推送过来的随机加密字符串(echostr),
        这一步注意作URL解码。
        2.验证消息体签名的正确性
        3. 解密出echostr原文，将原文当作Get请求的response，返回给企业微信
        第2，3步可以用企业微信提供的库函数VerifyURL来实现。

*/
// CallbackVerifyURL 企业微信验证回调URL
// 参考 https://work.weixin.qq.com/api/doc/90000/90135/90930
func (wxcpt *CallbackManager) CallbackVerifyURL(req CallbackVerifyURLReq) (string, error) {
	echoStr, cryptErr := wxcpt.crypt.verifyURL(req.MsgSignature, req.Timestamp, req.Nonce, req.EchoStr)
	if cryptErr != nil {
		err := fmt.Errorf("CallbackVerifyURL error : errcode=%v , errormsg=%v", cryptErr.ErrCode, cryptErr.ErrMsg)
		return "", err
	}
	return string(echoStr), nil

}

/*
   	------------使用示例二：对用户回复的消息解密---------------
   	用户回复消息或者点击事件响应时，企业会收到回调消息，此消息是经过企业微信加密之后的密文以post形式发送给企业，密文格式请参考官方文档
   	假设企业收到企业微信的回调消息如下：
   	POST /cgi-bin/wxpush? msg_signature=477715d11cdb4164915debcba66cb864d751f3e6&timestamp=1409659813&nonce=1372623149 HTTP/1.1
   	Host: qy.weixin.qq.com
   	Content-Length: 613
   	<xml>		<ToUserName><![CDATA[wx5823bf96d3bd56c7]]></ToUserName><Encrypt><![CDATA[RypEvHKD8QQKFhvQ6QleEB4J58tiPdvo+rtK1I9qca6aM/wvqnLSV5zEPeusUiX5L5X/0lWfrf0QADHHhGd3QczcdCUpj911L3vg3W/sYYvuJTs3TUUkSUXxaccAS0qhxchrRYt66wiSpGLYL42aM6A8dTT+6k4aSknmPj48kzJs8qLjvd4Xgpue06DOdnLxAUHzM6+kDZ+HMZfJYuR+LtwGc2hgf5gsijff0ekUNXZiqATP7PF5mZxZ3Izoun1s4zG4LUMnvw2r+KqCKIw+3IQH03v+BCA9nMELNqbSf6tiWSrXJB3LAVGUcallcrw8V2t9EL4EhzJWrQUax5wLVMNS0+rUPA3k22Ncx4XXZS9o0MBH27Bo6BpNelZpS+/uh9KsNlY6bHCmJU9p8g7m3fVKn28H3KDYA5Pl/T8Z1ptDAVe0lXdQ2YoyyH2uyPIGHBZZIs2pDBS8R07+qN+E7Q==]]></Encrypt>
   	<AgentID><![CDATA[218]]></AgentID>
   	</xml>

   	企业收到post请求之后应该：
        1.解析出url上的参数，包括消息体签名(msg_signature)，时间戳(timestamp)以及随机数字串(nonce)
        2.验证消息体签名的正确性。
        3.将post请求的数据进行xml解析，并将<Encrypt>标签的内容进行解密，解密出来的明文即是用户回复消息的明文，明文格式请参考官方文档
        第2，3步可以用企业微信提供的库函数DecryptMsg来实现。
*/

// GetUserMsgType 获取用户发送消息的消息类型
func (wxcpt *CallbackManager) GetUserMsgType(req DecryptUserMsgReq) (string, error) {
	var msgContent MsgTypeResp
	msg, cryptErr := wxcpt.crypt.decryptMsg(req.MsgSignature, req.Timestamp, req.Nonce, req.Data)
	if cryptErr != nil {
		err := fmt.Errorf("GetUserMsgType error : errcode=%v , errormsg=%v", cryptErr.ErrCode, cryptErr.ErrMsg)
		return "", err
	}
	err := xml.Unmarshal(msg, &msgContent)
	if err != nil {
		return "", err
	}
	return msgContent.MsgType, nil
}

// GetUserEventType 获取用户事件的事件类型，必须先判断消息类型为event，才能调用该函数
func (wxcpt *CallbackManager) GetUserEventType(req DecryptUserMsgReq) (string, error) {
	var msgContent EventTypeResp
	msg, cryptErr := wxcpt.crypt.decryptMsg(req.MsgSignature, req.Timestamp, req.Nonce, req.Data)
	if cryptErr != nil {
		err := fmt.Errorf("GetUserEventType error : errcode=%v , errormsg=%v", cryptErr.ErrCode, cryptErr.ErrMsg)
		return "", err
	}
	err := xml.Unmarshal(msg, &msgContent)
	if err != nil {
		return "", err
	}
	return msgContent.Event, nil
}

// DecryptUserMsg 对用户回复的文本消息解密
func (wxcpt *CallbackManager) DecryptUserMsg(req DecryptUserMsgReq) (*MsgContent, error) {
	var msgContent MsgContent
	msg, cryptErr := wxcpt.crypt.decryptMsg(req.MsgSignature, req.Timestamp, req.Nonce, req.Data)
	if cryptErr != nil {
		err := fmt.Errorf("DecryptUserMsg error : errcode=%v , errormsg=%v", cryptErr.ErrCode, cryptErr.ErrMsg)
		return nil, err
	}
	err := xml.Unmarshal(msg, &msgContent)
	if err != nil {
		return nil, err
	}
	return &msgContent, nil
}

// DecryptVoiceMsg 对用户回复的语音消息解密
func (wxcpt *CallbackManager) DecryptVoiceMsg(req DecryptUserMsgReq) (*MsgVoice, error) {
	var msgContent MsgVoice
	msg, cryptErr := wxcpt.crypt.decryptMsg(req.MsgSignature, req.Timestamp, req.Nonce, req.Data)
	if cryptErr != nil {
		err := fmt.Errorf("DecryptVoiceMsg error : errcode=%v , errormsg=%v", cryptErr.ErrCode, cryptErr.ErrMsg)
		return nil, err
	}
	err := xml.Unmarshal(msg, &msgContent)
	if err != nil {
		return nil, err
	}
	return &msgContent, nil
}

// DecryptImageMsg 对用户回复的图片消息解密
func (wxcpt *CallbackManager) DecryptImageMsg(req DecryptUserMsgReq) (*MsgImage, error) {
	var msgContent MsgImage
	msg, cryptErr := wxcpt.crypt.decryptMsg(req.MsgSignature, req.Timestamp, req.Nonce, req.Data)
	if cryptErr != nil {
		err := fmt.Errorf("DecryptUserMsg error : errcode=%v , errormsg=%v", cryptErr.ErrCode, cryptErr.ErrMsg)
		return nil, err
	}
	err := xml.Unmarshal(msg, &msgContent)
	if err != nil {
		return nil, err
	}
	return &msgContent, nil
}

// DecryptVideoMsg 对用户回复的视频消息解密
func (wxcpt *CallbackManager) DecryptVideoMsg(req DecryptUserMsgReq) (*MsgVideo, error) {
	var msgContent MsgVideo
	msg, cryptErr := wxcpt.crypt.decryptMsg(req.MsgSignature, req.Timestamp, req.Nonce, req.Data)
	if cryptErr != nil {
		err := fmt.Errorf("DecryptVideoMsg error : errcode=%v , errormsg=%v", cryptErr.ErrCode, cryptErr.ErrMsg)
		return nil, err
	}
	err := xml.Unmarshal(msg, &msgContent)
	if err != nil {
		return nil, err
	}
	return &msgContent, nil
}

// DecryptLocationMsg 对用户回复的位置消息解密
func (wxcpt *CallbackManager) DecryptLocationMsg(req DecryptUserMsgReq) (*MsgLocation, error) {
	var msgContent MsgLocation
	msg, cryptErr := wxcpt.crypt.decryptMsg(req.MsgSignature, req.Timestamp, req.Nonce, req.Data)
	if cryptErr != nil {
		err := fmt.Errorf("DecryptLocationMsg error : errcode=%v , errormsg=%v", cryptErr.ErrCode, cryptErr.ErrMsg)
		return nil, err
	}
	err := xml.Unmarshal(msg, &msgContent)
	if err != nil {
		return nil, err
	}
	return &msgContent, nil
}

// DecryptLinkMsg 对用户回复的链接消息解密
func (wxcpt *CallbackManager) DecryptLinkMsg(req DecryptUserMsgReq) (*MsgLink, error) {
	var msgContent MsgLink
	msg, cryptErr := wxcpt.crypt.decryptMsg(req.MsgSignature, req.Timestamp, req.Nonce, req.Data)
	if cryptErr != nil {
		err := fmt.Errorf("DecryptLinkMsg error : errcode=%v , errormsg=%v", cryptErr.ErrCode, cryptErr.ErrMsg)
		return nil, err
	}
	err := xml.Unmarshal(msg, &msgContent)
	if err != nil {
		return nil, err
	}
	return &msgContent, nil
}

// DecryptSubEvent 对订阅事件解密
func (wxcpt *CallbackManager) DecryptSubEvent(req DecryptUserMsgReq) (*EventSub, error) {
	var msgContent EventSub
	msg, cryptErr := wxcpt.crypt.decryptMsg(req.MsgSignature, req.Timestamp, req.Nonce, req.Data)
	if cryptErr != nil {
		err := fmt.Errorf("DecryptSubEvent error : errcode=%v , errormsg=%v", cryptErr.ErrCode, cryptErr.ErrMsg)
		return nil, err
	}
	err := xml.Unmarshal(msg, &msgContent)
	if err != nil {
		return nil, err
	}
	return &msgContent, nil
}

// DecryptEnterAgentEvent 对进入应用事件解密
func (wxcpt *CallbackManager) DecryptEnterAgentEvent(req DecryptUserMsgReq) (*EventEnterAgent, error) {
	var msgContent EventEnterAgent
	msg, cryptErr := wxcpt.crypt.decryptMsg(req.MsgSignature, req.Timestamp, req.Nonce, req.Data)
	if cryptErr != nil {
		err := fmt.Errorf("DecryptEnterAgentEvent error : errcode=%v , errormsg=%v", cryptErr.ErrCode, cryptErr.ErrMsg)
		return nil, err
	}
	err := xml.Unmarshal(msg, &msgContent)
	if err != nil {
		return nil, err
	}
	return &msgContent, nil
}

// DecryptReportLocationEvent 对上报位置事件解密
func (wxcpt *CallbackManager) DecryptReportLocationEvent(req DecryptUserMsgReq) (*EventReportLocation, error) {
	var msgContent EventReportLocation
	msg, cryptErr := wxcpt.crypt.decryptMsg(req.MsgSignature, req.Timestamp, req.Nonce, req.Data)
	if cryptErr != nil {
		err := fmt.Errorf("DecryptReportLocationEvent error : errcode=%v , errormsg=%v", cryptErr.ErrCode, cryptErr.ErrMsg)
		return nil, err
	}
	err := xml.Unmarshal(msg, &msgContent)
	if err != nil {
		return nil, err
	}
	return &msgContent, nil
}

/*
   	------------使用示例三：企业回复用户消息的加密---------------
   	企业被动回复用户的消息也需要进行加密，并且拼接成密文格式的xml串。
   	假设企业需要回复用户的明文如下：
   	<xml>
   	<ToUserName><![CDATA[mycreate]]></ToUserName>
   	<FromUserName><![CDATA[wx5823bf96d3bd56c7]]></FromUserName>
   	<CreateTime>1348831860</CreateTime>
   	<MsgType><![CDATA[text]]></MsgType>
   	<Content><![CDATA[this is a test]]></Content>
   	<MsgId>1234567890123456</MsgId>
   	<AgentID>128</AgentID>
   	</xml>

   	为了将此段明文回复给用户，企业应：
        1.自己生成时间时间戳(timestamp),随机数字串(nonce)以便生成消息体签名，也可以直接用从企业微信的post url上解析出的对应值。
        2.将明文加密得到密文。
        3.用密文，步骤1生成的timestamp,nonce和企业在企业微信设定的token生成消息体签名。
        4.将密文，消息体签名，时间戳，随机数字串拼接成xml格式的字符串，发送给企业。
        以上2，3，4步可以用企业微信提供的库函数EncryptMsg来实现。
*/

// EncryptUserMsg 企业回复的消息加密
func (wxcpt *CallbackManager) EncryptUserMsg(req EncryptUserMsgReq) (string, error) {
	encryptMsg, cryptErr := wxcpt.crypt.encryptMsg(req.Data, req.Timestamp, req.Nonce)
	if cryptErr != nil {
		err := fmt.Errorf("EncryptUserMsg error : errcode=%v , errormsg=%v", cryptErr.ErrCode, cryptErr.ErrMsg)
		return "", err
	}

	return string(encryptMsg), nil
}

// EncryptUserMsg 回复文本消息
func (wxcpt *CallbackManager) ReplyUserMsgContent(req MsgContentReply) (string, error) {
	timestamp := strconv.FormatInt(time.Now().Unix(), 10)
	data, err := xml.Marshal(&req)
	if err != nil {
		return "", err
	}
	nonce := strconv.FormatInt(time.Now().Unix(), 10)
	userReply := EncryptUserMsgReq{
		Timestamp: timestamp,
		Nonce:     nonce,
		Data:      string(data),
	}
	encryptMsg, cryptErr := wxcpt.crypt.encryptMsg(userReply.Data, userReply.Timestamp, userReply.Nonce)
	if cryptErr != nil {
		err := fmt.Errorf("EncryptUserMsg error : errcode=%v , errormsg=%v", cryptErr.ErrCode, cryptErr.ErrMsg)
		return "", err
	}
	return string(encryptMsg), nil
}

// ReplyUserMsgImage 回复图片消息
func (wxcpt *CallbackManager) ReplyUserMsgImage(req MsgImageReply) (string, error) {
	timestamp := strconv.FormatInt(time.Now().Unix(), 10)
	data, err := xml.Marshal(&req)
	if err != nil {
		return "", err
	}
	nonce := strconv.FormatInt(time.Now().Unix(), 10)
	userReply := EncryptUserMsgReq{
		Timestamp: timestamp,
		Nonce:     nonce,
		Data:      string(data),
	}
	encryptMsg, cryptErr := wxcpt.crypt.encryptMsg(userReply.Data, userReply.Timestamp, userReply.Nonce)
	if cryptErr != nil {
		err := fmt.Errorf("EncryptUserMsg error : errcode=%v , errormsg=%v", cryptErr.ErrCode, cryptErr.ErrMsg)
		return "", err
	}
	return string(encryptMsg), nil
}

// ReplyUserMsgVoice 回复语音消息
func (wxcpt *CallbackManager) ReplyUserMsgVoice(req MsgVoiceReply) (string, error) {
	timestamp := strconv.FormatInt(time.Now().Unix(), 10)
	data, err := xml.Marshal(&req)
	if err != nil {
		return "", err
	}
	nonce := strconv.FormatInt(time.Now().Unix(), 10)
	userReply := EncryptUserMsgReq{
		Timestamp: timestamp,
		Nonce:     nonce,
		Data:      string(data),
	}
	encryptMsg, cryptErr := wxcpt.crypt.encryptMsg(userReply.Data, userReply.Timestamp, userReply.Nonce)
	if cryptErr != nil {
		err := fmt.Errorf("EncryptUserMsg error : errcode=%v , errormsg=%v", cryptErr.ErrCode, cryptErr.ErrMsg)
		return "", err
	}
	return string(encryptMsg), nil
}

// ReplyUserMsgVideo 回复视频消息
func (wxcpt *CallbackManager) ReplyUserMsgVideo(req MsgVideoReply) (string, error) {
	timestamp := strconv.FormatInt(time.Now().Unix(), 10)
	data, err := xml.Marshal(&req)
	if err != nil {
		return "", err
	}
	nonce := strconv.FormatInt(time.Now().Unix(), 10)
	userReply := EncryptUserMsgReq{
		Timestamp: timestamp,
		Nonce:     nonce,
		Data:      string(data),
	}
	encryptMsg, cryptErr := wxcpt.crypt.encryptMsg(userReply.Data, userReply.Timestamp, userReply.Nonce)
	if cryptErr != nil {
		err := fmt.Errorf("EncryptUserMsg error : errcode=%v , errormsg=%v", cryptErr.ErrCode, cryptErr.ErrMsg)
		return "", err
	}
	return string(encryptMsg), nil
}

// ReplyUserMsgNews 回复图文消息
func (wxcpt *CallbackManager) ReplyUserMsgNews(req MsgNewsReply) (string, error) {
	timestamp := strconv.FormatInt(time.Now().Unix(), 10)
	data, err := xml.Marshal(&req)
	if err != nil {
		return "", err
	}
	nonce := strconv.FormatInt(time.Now().Unix(), 10)
	userReply := EncryptUserMsgReq{
		Timestamp: timestamp,
		Nonce:     nonce,
		Data:      string(data),
	}
	encryptMsg, cryptErr := wxcpt.crypt.encryptMsg(userReply.Data, userReply.Timestamp, userReply.Nonce)
	if cryptErr != nil {
		err := fmt.Errorf("EncryptUserMsg error : errcode=%v , errormsg=%v", cryptErr.ErrCode, cryptErr.ErrMsg)
		return "", err
	}
	return string(encryptMsg), nil
}

// ReplyUserMsgTaskCard 回复任务卡片
func (wxcpt *CallbackManager) ReplyUserMsgTaskCard(req MsgTaskCardReply) (string, error) {
	timestamp := strconv.FormatInt(time.Now().Unix(), 10)
	data, err := xml.Marshal(&req)
	if err != nil {
		return "", err
	}
	nonce := strconv.FormatInt(time.Now().Unix(), 10)
	userReply := EncryptUserMsgReq{
		Timestamp: timestamp,
		Nonce:     nonce,
		Data:      string(data),
	}
	encryptMsg, cryptErr := wxcpt.crypt.encryptMsg(userReply.Data, userReply.Timestamp, userReply.Nonce)
	if cryptErr != nil {
		err := fmt.Errorf("EncryptUserMsg error : errcode=%v , errormsg=%v", cryptErr.ErrCode, cryptErr.ErrMsg)
		return "", err
	}
	return string(encryptMsg), nil
}
